/*----------------------------------------------------------------------------
 * Copyright (c) <2013-2015>, <Huawei Technologies Co., Ltd>
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 * of conditions and the following disclaimer in the documentation and/or other materials
 * provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 * to endorse or promote products derived from this software without specific prior written
 * permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *---------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------
 * Notice of Export Control Law
 * ===============================================
 * Huawei LiteOS may be subject to applicable export control laws and regulations, which might
 * include those applicable to Huawei LiteOS of U.S. and the country in which you are located.
 * Import, export and usage of Huawei LiteOS in any manner by you shall be in compliance with such
 * applicable export control laws and regulations.
 *---------------------------------------------------------------------------*/

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "asm/errno.h"

#include "spinor_common.h"
#include <linux/mtd/mtd.h>

#include "hisoc/flash.h"

#include "host_common.h"
#include "../src/common/spinor_common.h"
#include "spi_common.h"
#include "spinor_ids.h"
#include "hifmc_common.h"
#include "hifmc100.h"

#include "reset_shell.h"

extern void hifmc100_shutdown(void);
extern u_char hifmc100_read_reg(struct spi *spi, u_char cmd);
/*---------------------------------------------------------------------------*/
/* hifmc100_read_id */
/*---------------------------------------------------------------------------*/
static void hifmc100_read_id(struct spinor_info *spinor, char *id)
{
    int reg;
    struct spinor_host *host = spinor->priv;

    reg = FMC_CMD_CMD1(SPI_CMD_RDID);
    reg_write(host, reg, FMC_CMD);

    reg = OP_CFG_FM_CS(spinor->cur_cs);
    reg_write(host, reg, FMC_OP_CFG);

    reg = FMC_DATA_NUM_CNT(SPI_NOR_MAX_ID_LEN);
    reg_write(host, reg, FMC_DATA_NUM);

    reg = FMC_OP_CMD1_EN(ENABLE)
          | FMC_OP_READ_DATA_EN(ENABLE)
          | FMC_OP_REG_OP_START;
    reg_write(host, reg, FMC_OP);

    FMC_CMD_WAIT_CPU_FINISH(host);

    memcpy_s(id, SPI_NOR_MAX_ID_LEN, host->membase, SPI_NOR_MAX_ID_LEN);
}

/*---------------------------------------------------------------------------*/
/* hifmc100_dma_transfer */
/*---------------------------------------------------------------------------*/
static void hifmc100_dma_transfer(struct spinor_host *host,
                                  uint32_t spi_start_addr, char *dma_buffer,
                                  uint8_t rw, uint32_t size)
{
    uint8_t if_type = 0, dummy = 0;
    uint8_t w_cmd = 0, r_cmd = 0;
    int reg;
    struct spi *spi = host->spi;

    MTD_PR(DMA_DBG, "\t\t *-Start dma transfer => [%#x], len[%#x].\n",
           spi_start_addr, size);
    if (rw == WRITE) {
        mtd_dma_cache_clean((void *)dma_buffer, size);
    } else {
        mtd_dma_cache_inv((void *)dma_buffer, size);
    }

    reg = FMC_INT_CLR_ALL;
    reg_write(host, reg, FMC_INT_CLR);

    reg = spi_start_addr;
    reg_write(host, reg, FMC_ADDRL);

    if (rw == WRITE) {
        if_type = spi->write->iftype;
        dummy = spi->write->dummy;
        w_cmd = spi->write->cmd;
    } else {
        if_type = spi->read->iftype;
        dummy = spi->read->dummy;
        r_cmd = spi->read->cmd;
    }

    reg = OP_CFG_FM_CS(spi->cs)
          | OP_CFG_MEM_IF_TYPE(if_type)
          | OP_CFG_ADDR_NUM(spi->addrcycle)
          | OP_CFG_DUMMY_NUM(dummy);
    reg_write(host, reg, FMC_OP_CFG);

    reg = FMC_DMA_LEN_SET(size);
    reg_write(host, reg, FMC_DMA_LEN);

    reg = (uint32_t)((AARCHPTR)dma_buffer);
    reg_write(host, reg, FMC_DMA_SADDR_D0);

//for the 64-register SADDRH of 3559a
#if defined LOSCFG_PLATFORM_HI3559AV100ES || defined LOSCFG_PLATFORM_HI3559AV100 || defined LOSCFG_PLATFORM_HI3556AV100
    reg = (uint32_t)((AARCHPTR)dma_buffer >> 32);
    reg_write(host, reg, FMC_DMA_SADDRH_D0);
#endif

    reg = OP_CTRL_RD_OPCODE(r_cmd)
          | OP_CTRL_WR_OPCODE(w_cmd)
          | OP_CTRL_RW_OP(rw)
          | OP_CTRL_DMA_OP_READY;
    reg_write(host, reg, FMC_OP_CTRL);

    FMC_DMA_WAIT_INT_FINISH(host);
    if (READ == rw) {
        mtd_dma_cache_inv((void *)dma_buffer, size);
    }
    return;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_dma_read */
/*---------------------------------------------------------------------------*/
static int hifmc100_dma_read(struct spinor_host *host, uint32_t from,
                             uint32_t len, const char *buf)
{
    int num, reg;
    struct spi *spi = host->spi;

    reg = spi->driver->wait_ready(spi);
    if (reg) {
        ERR_MSG(" Dma read wait ready fail! reg:%#x\n", reg);
        return -EBUSY;
    }

    host->set_system_clock(spi->read->clock, ENABLE);

    if ((unsigned long)buf & HIFMC100_DMA_ALIGN_MASK) {
        num = HIFMC100_DMA_ALIGN_SIZE -
              ((unsigned long)buf & (HIFMC100_DMA_ALIGN_MASK));
        if (num > len) {
            num = len;
        }
        hifmc100_dma_transfer(host, from,
                              (char *)host->dma_buffer, READ, num);
        memcpy_s((void *)buf, len, (void *)host->dma_buffer, num);
        from  += num;
        buf += num;
        len -= num;
    }

    if ((num = len & (~HIFMC100_DMA_ALIGN_MASK))) {
        hifmc100_dma_transfer(host, from, (char *)buf, READ, num);
        buf  += num;
        from += num;
        len  -= num;
    }

    if (len) {
        num = len;
        hifmc100_dma_transfer(host, from,
                              (char *)host->dma_buffer, READ, num);
        memcpy_s((void *)buf, len, (void *)host->dma_buffer, num);
        buf  += num;
        len  -= num;
    }

    return 0;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_read */
/*---------------------------------------------------------------------------*/
static int hifmc100_read(struct spinor_info *spinor, uint32_t from,
                         uint32_t len, const char *buf)
{
    int result;
    struct spinor_host *host = spinor->priv;

    MTD_PR(ER_DBG, "Start read buf=0x%08x from=0x%08x len=0x%08x.\n",
           buf, from, len);
    get_host(host);
    result = hifmc100_dma_read(host, from, len, buf);
    put_host(host);

    return result;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_dma_write */
/*---------------------------------------------------------------------------*/
static int hifmc100_dma_write(struct spinor_host *host, uint32_t to,
                              uint32_t len, const char *buf)
{
    int num, reg;
    struct spi *spi = host->spi;

    reg = spi->driver->wait_ready(spi);
    if (reg) {
        ERR_MSG("Dma write wait ready fail! reg:%#x\n", reg);
        return -EBUSY;
    }
    spi->driver->write_enable(spi);

    host->set_system_clock(spi->write->clock, ENABLE);

    if ((unsigned long)buf & HIFMC100_DMA_ALIGN_MASK) {
        num = HIFMC100_DMA_ALIGN_SIZE -
              ((unsigned long)buf & (HIFMC100_DMA_ALIGN_MASK));
        if (num > len) {
            num = len;
        }
        memcpy_s((void *)host->dma_buffer, len, (void *)buf, num);
        hifmc100_dma_transfer(host, to,
                              (char *)host->dma_buffer, WRITE, num);

        to  += num;
        buf += num;
        len -= num;
    }

    if ((num = len & (~HIFMC100_DMA_ALIGN_MASK))) {
        hifmc100_dma_transfer(host, to, (char *)buf, WRITE, num);
        to  += num;
        buf += num;
        len -= num;
    }

    if (len) {
        num = len;
        memcpy_s((void *)host->dma_buffer, len, (void *)buf, num);
        hifmc100_dma_transfer(host, to,
                              (char *)host->dma_buffer, WRITE, num);

        buf += num;
        len -= num;
    }

    return 0;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_write */
/*---------------------------------------------------------------------------*/
static int hifmc100_write(struct spinor_info *spinor, uint32_t to, uint32_t len,
                          const char *buf)
{
    int result;
    struct spinor_host *host = spinor->priv;

    MTD_PR(ER_DBG, "Start write buf=0x%08x to=0x%08x len=0x%08x.\n", buf, to, len);
    get_host(host);
    result = hifmc100_dma_write(host, to, len, buf);
    put_host(host);

    return result;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_erase_one_block */
/*---------------------------------------------------------------------------*/
static int hifmc100_erase_one_block(struct spinor_host *host, uint32_t offset)
{
    int reg;
    struct spi *spi = host->spi;

    reg = spi->driver->wait_ready(spi);
    if (reg) {
        ERR_MSG("Erase wait ready fail! reg:%#x\n", reg);
        return -EBUSY;
    }
    spi->driver->write_enable(spi);

    host->set_system_clock(spi->erase->clock, ENABLE);

    reg = FMC_CMD_CMD1(spi->erase->cmd);
    reg_write(host, reg, FMC_CMD);

    reg = offset;
    reg_write(host, reg, FMC_ADDRL);

    reg = OP_CFG_FM_CS(spi->cs)
          | OP_CFG_MEM_IF_TYPE(spi->erase->iftype)
          | OP_CFG_ADDR_NUM(spi->addrcycle)
          | OP_CFG_DUMMY_NUM(spi->erase->dummy);
    reg_write(host, reg, FMC_OP_CFG);

    reg = FMC_OP_CMD1_EN(ENABLE)
          | FMC_OP_ADDR_EN(ENABLE)
          | FMC_OP_REG_OP_START;
    reg_write(host, reg, FMC_OP);

    FMC_CMD_WAIT_CPU_FINISH(host);

    return 0;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_erase */
/*---------------------------------------------------------------------------*/
static int hifmc100_erase(struct spinor_info *spinor, uint32_t addr,
                          uint32_t len)
{
    struct spinor_host *host = spinor->priv;
    get_host(host);
    while (len) {
        MTD_PR(ER_DBG, "Start erase one block, addr=[0x%08x].\n", addr);
        if (hifmc100_erase_one_block(host, addr)) {
            return -EIO;
        }
        addr += spinor->dev.blocksize;
        len -= spinor->dev.blocksize;
    }
    put_host(host);
    return 0;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_spi_op_map */
/*---------------------------------------------------------------------------*/
void hifmc100_spi_op_map(struct spi *spi)
{
    int ix;
    const int iftype_read[] = {
        SPI_IF_READ_STD,        SPI_IF_TYPE_STD,
        SPI_IF_READ_FAST,       SPI_IF_TYPE_STD,
        SPI_IF_READ_DUAL,       SPI_IF_TYPE_DUAL,
        SPI_IF_READ_DUAL_ADDR,  SPI_IF_TYPE_DIO,
        SPI_IF_READ_QUAD,       SPI_IF_TYPE_QUAD,
        SPI_IF_READ_QUAD_ADDR,  SPI_IF_TYPE_QIO,
#ifdef LOSCFG_DRIVERS_DTR_MODE_SUPPORT
        SPI_IF_READ_QUAD_DTR,   SPI_IF_TYPE_DTR,
#endif
        0,            0,
    };
    const int iftype_write[] = {
        SPI_IF_WRITE_STD,       SPI_IF_TYPE_STD,
        SPI_IF_WRITE_DUAL,      SPI_IF_TYPE_DUAL,
        SPI_IF_WRITE_DUAL_ADDR, SPI_IF_TYPE_DIO,
        SPI_IF_WRITE_QUAD,      SPI_IF_TYPE_QUAD,
        SPI_IF_WRITE_QUAD_ADDR, SPI_IF_TYPE_QIO,
        0,            0,
    };

    for (ix = 0; iftype_write[ix]; ix += 2) {
        if (spi->write->iftype == iftype_write[ix]) {
            spi->write->iftype = iftype_write[ix + 1];
            break;
        }
    }

    for (ix = 0; iftype_read[ix]; ix += 2) {
        if (spi->read->iftype == iftype_read[ix]) {
            spi->read->iftype = iftype_read[ix + 1];
            break;
        }
    }

    spi->erase->iftype = SPI_IF_TYPE_STD;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_ids_probe */
/*---------------------------------------------------------------------------*/
static void hifmc100_ids_probe(struct spinor_info *spinor)
{
    struct spinor_host *host = spinor->priv;
    struct spi *spi = host->spi;
    struct spi_nor_info *spiinfo = spinor->dev.priv;
    char *ids = spinor->dev.id;
    unsigned int regval;
    unsigned char cval, sval, dtr_flag;
    unsigned short cs_reg;
    dtr_flag = 0;

    MTD_PR(INIT_DBG, "\t|*-Start match SPI operation & chip init\n");
    spi->name = spiinfo->name;
    spi->cs = spinor->cur_cs;
    spi->chipsize = spiinfo->chipsize;
    spi->erasesize = spiinfo->erasesize;
    spi->addrcycle = spiinfo->addrcycle;
    spi->driver = spiinfo->driver;
    spi->host = host;

#ifdef LOSCFG_DRIVERS_SPI_BLOCK_PROTECT
    host->cmp = BP_CMP_UPDATE_FLAG;
    hifmc100_get_bp_lock_level(host);
#endif

    regval = reg_read(host, FMC_GLOBAL_CFG);
    dtr_flag = (regval >> DTR_MODE_REQUEST_SHIFT) & 0x1;

#ifdef LOSCFG_DRIVERS_DTR_MODE_SUPPORT
    hifmc_check_spi_dtr_support(spi, ids);
    if (spi->dtr_mode_support && dtr_flag) {
        /* to match the best dummy/if_type/clock */
        spi_nor_search_rw(spiinfo, spi->read,
                          SPI_NOR_SUPPORT_READ,
                          SPI_NOR_DTR_MAX_DUMMY, READ);
        hifmc_get_fmc_best_4x_clock(&spi->read->clock);

    } else {
        if (!spi->dtr_mode_support) {
            printf("\e[0;32mnote:this spi nor flash does not support DTR mode!!\033[0m\n");
        }
        if (!dtr_flag) {
            printf("\e[0;32mnote:before you use DTR mode in liteos,you must enable DTR mode both u-boot and liteos!!\033[0m\n");
        }

        spi_nor_search_rw(spiinfo, spi->read,
                          SPI_NOR_SUPPORT_READ,
                          SPI_NOR_STR_MAX_DUMMY, READ);

        hifmc100_get_best_clock(&spi->read->clock);
    }

#else
    /* for the situation where users enable DTR mode in uboot and disable DTR mode in liteos,so we switch to STR model */
    if (dtr_flag) {
        regval &= (~(1 << DTR_MODE_REQUEST_SHIFT));
        reg_write(host, regval, FMC_GLOBAL_CFG);
    }
    /*clear dummy_cycle bits for MXIC's spi nor flash */
    if (ids[0] == MID_MXIC) {
        cval = hifmc100_read_reg(spi, SPI_CMD_RDCR_MX);
        if (cval & CR_DUMMY_CYCLE) {
            cval &= (~CR_DUMMY_CYCLE);
            spi->driver->write_enable(spi);
            sval = hifmc100_read_reg(spi, SPI_CMD_RDSR);

            cs_reg = ((unsigned short)cval << 8) | sval;
            writew(cs_reg, host->membase);
            hisi_spi_nor_op_reg(spi, SPI_CMD_WRSR, 2, FMC_OP_WRITE_DATA_EN(ENABLE));

        }
    }
    /* to match the best dummy/if_type/clock */
    spi_nor_search_rw(spiinfo, spi->read,
                      SPI_NOR_SUPPORT_READ,
                      SPI_NOR_STR_MAX_DUMMY, READ);

    hifmc100_get_best_clock(&spi->read->clock);
#endif

    spi_nor_search_rw(spiinfo, spi->write,
                      SPI_NOR_SUPPORT_WRITE,
                      SPI_NOR_SUPPORT_MAX_DUMMY, WRITE);

    spi_nor_get_erase(spiinfo, spi->erase);

    hifmc100_spi_op_map(spi);
    hifmc100_get_best_clock(&spi->write->clock);
    hifmc100_get_best_clock(&spi->erase->clock);

    spi->driver->qe_enable(spi);
    /* auto check fmc_addr_mode 3 bytes or 4 bytes */
    if (GET_FMC_BOOT_MODE == SPI_NOR_ADDR_MODE_3_BYTES) {
        MTD_PR(INIT_DBG, "start up mode is 3 bytes\n");
        spi->driver->entry_4addr(spi, ENABLE);
    } else {
        MTD_PR(INIT_DBG, "start up mode is 4 bytes\n");
    }

    MTD_PR(INIT_DBG, "Spi(cs%d):", spi->cs);
    MTD_PR(INIT_DBG, "Block:%sB ", ulltostr(spi->erasesize));
    MTD_PR(INIT_DBG, "Chip:%sB ", ulltostr(spi->chipsize));
    MTD_PR(INIT_DBG, "Name:\"%s\"\n", spi->name);

    MTD_PR(DTR_DBG, "\nspi read iftype:%#x\n", spi->read->iftype);
    MTD_PR(DTR_DBG, "\nspi read cmd:%#x\n", spi->read->cmd);
    MTD_PR(DTR_DBG, "\nspi read dummy:%d\n", spi->read->dummy);

    MTD_PR(INIT_DBG, "\t  *-End match SPI operation & chip init\n");
}


/*---------------------------------------------------------------------------*/
/* hifmc100_set_host_addr_mode */
/*---------------------------------------------------------------------------*/
static void hifmc100_set_host_addr_mode(struct spinor_host *host, int enable)
{
    uint32_t reg;

    reg = reg_read(host, FMC_CFG);
    if (enable) {
        reg |= FMC_SPI_NOR_ADDR_MODE_MASK;
    } else {
        reg &= ~FMC_SPI_NOR_ADDR_MODE_MASK;
    }

    reg_write(host, reg, FMC_CFG);
}

/*---------------------------------------------------------------------------*/
/* hifmc100_host_init */
/*---------------------------------------------------------------------------*/
static int hifmc100_host_init(struct spinor_host *host)
{
    int reg, flash_type;
    MTD_PR(INIT_DBG, "\t||*-Start SPI Nor host init\n");

    if (LOS_OK != LOS_MuxCreate(&host->lock)) {
        return -1;
    }

    reg = reg_read(host, FMC_CFG);
    flash_type = (reg & FLASH_SEL_MASK) >> FLASH_SEL_SHIFT;
    if (flash_type != FLASH_TYPE_SPI_NOR) {
        WARN_MSG("Flash type isn't Spi Nor!\n");
        return -ENODEV;
    }

    if ((reg & OP_MODE_MASK) == OP_MODE_BOOT) {
        reg |= FMC_CFG_OP_MODE(OP_MODE_NORMAL);
    }
    reg_write(host, reg, FMC_CFG);

    host->set_system_clock = hifmc100_set_system_clock;
    host->set_host_addr_mode = hifmc100_set_host_addr_mode;

    host->buffer = memalign(CACHE_ALIGNED_SIZE,
                            ALIGN(CACHE_ALIGNED_SIZE, CACHE_ALIGNED_SIZE));
    if (!host->buffer) {
        ERR_MSG("no mem for hifmc dma mem!\n");
        return -ENOMEM;
    }
    host->dma_buffer = host->buffer;

    host->set_system_clock(0, ENABLE);

    reg = TIMING_CFG_TCSH(CS_HOLD_TIME)
          | TIMING_CFG_TCSS(CS_SETUP_TIME)
          | TIMING_CFG_TSHSL(CS_DESELECT_TIME);
    reg_write(host, reg, FMC_SPI_TIMING_CFG);

    reg = ALL_BURST_ENABLE;
    reg_write(host, reg, FMC_DMA_AHB_CTRL);

    MTD_PR(INIT_DBG, "\t||*-End SPI Nor host init\n");
#ifdef  LOSCFG_SHELL
    (void)osReHookFuncAdd((STORAGE_HOOK_FUNC)hifmc100_shutdown, NULL);
#endif
    return 0;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_resume - resume host */
/*---------------------------------------------------------------------------*/
static int hifmc100_resume(struct spinor_info *spinor)
{
    struct spinor_host *host = spinor->priv;

    put_host(host);
    MTD_PR(INIT_DBG, "\t hifmc100_resume ok\n");
    return 0;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_spinor_init - spinor info member init */
/*---------------------------------------------------------------------------*/
static void hifmc100_spinor_init(struct spinor_info *spinor)
{
    spinor->erase = hifmc100_erase;
    spinor->write = hifmc100_write;
    spinor->read = hifmc100_read;

    spinor->read_id = hifmc100_read_id;
    spinor->ids_probe = hifmc100_ids_probe;
    spinor->resume = hifmc100_resume;
#ifdef LOSCFG_DRIVERS_SPI_BLOCK_PROTECT
    spinor->lock = hifmc100_spi_flash_lock;
#endif
    spinor->cur_cs = HIFMC100_SPI_NOR_CS_NUM;
}

/*---------------------------------------------------------------------------*/
/* spinor_host_init - spinor controller initializtion entry */
/*---------------------------------------------------------------------------*/
int spinor_host_init(struct spinor_host *host)
{
    hifmc100_spinor_init(host->spinor);

    if (hifmc100_host_init(host)) {
        ERR_MSG("hifmc100 host init fail!\n");
        return -1;
    }

    return 0;
}

#ifdef LOSCFG_DRIVERS_DTR_MODE_SUPPORT
/*---------------------------------------------------------------------------*/
/* hifmc_check_spi_dtr_support */
/*---------------------------------------------------------------------------*/
void hifmc_check_spi_dtr_support(struct spi *spi, u_char *ids)
{
    unsigned manu_id = ids[0], dev_id = ids[1];

    spi->dtr_mode_support = 0;
    spi->dtr_cookie = DTR_MODE_SET_NONE;

    switch (manu_id) {
        case MID_MXIC:
            if (spi_mxic_check_spi_dtr_support(spi)) {
                spi->dtr_cookie = DTR_MODE_SET_ODS;
                goto dtr_on;
            }
            break;
        case MID_WINBOND:
            /* Device ID: 0x70 means support DTR Mode for Winbond */
            if (dev_id == DEVICE_ID_SUPPORT_DTR_WINBOND) {
                spi->dtr_mode_support = 1;
                goto dtr_on;
            }
            break;
        default:
            break;
    }

    MTD_PR(DTR_DBG, "The Double Transfer Rate Read Mode isn't supported.\n");
    return;

dtr_on:
    printf("The Double Transfer Rate Read Mode is supported.\n");
}
#endif

/*---------------------------------------------------------------------------*/
/* hisi_spi_nor_op_reg */
/*---------------------------------------------------------------------------*/
int hisi_spi_nor_op_reg(struct spi *spi, unsigned char opcode, unsigned int len, unsigned char optype)
{

    struct spinor_host *host = (struct spinor_host *)spi->host;
    u32 reg;

    reg = FMC_CMD_CMD1(opcode);
    reg_write(host, reg, FMC_CMD);

    reg = FMC_DATA_NUM_CNT(len);
    reg_write(host, reg, FMC_DATA_NUM);

    reg = OP_CFG_FM_CS(spi->cs) | OP_CFG_OEN_EN;
    reg_write(host, reg, FMC_OP_CFG);

    reg = FMC_OP_CMD1_EN(1) | FMC_OP_REG_OP_START | optype;
    reg_write(host, reg, FMC_OP);

    FMC_CMD_WAIT_CPU_FINISH(host);


    return 0;
}

#ifdef LOSCFG_DRIVERS_SPI_BLOCK_PROTECT
/*---------------------------------------------------------------------------*/
/* hifmc100_spi_flash_lock */
/*---------------------------------------------------------------------------*/
void hifmc100_spi_flash_lock(struct spinor_info *spinor, unsigned char cmp, unsigned char level,
                             unsigned char op)
{
    struct spinor_host *host = spinor->priv;
    struct spinor_info *nor = host->spinor;

    host->cmp = cmp;

    if (BP_OP_GET == op) {
        puts("Get spi lock information\n");
        if (host->level) {
            if (host->level == nor->bp_level_max) {
                puts("all blocks are locked.\n");
            } else {
                printf("level: %d\n", host->level);
            }
            printf("Spi is locked. lock address[0 => %#x]\n",
                   host->end_addr);
        } else {
            puts("all blocks are unlocked.\n");
        }

        return;
    }

    if (BP_OP_SET == op) {
        if (level) {
            if (level == nor->bp_level_max) {
                puts("lock all blocks.\n");
            } else {
                printf("lock level: %d\n", level);
            }
        } else {
            puts("unlock all block.\n");
        }

        hifmc100_spi_lock(host, level);
        return;
    }

    printf("%s ERROR: Invalid optin argument!", __func__);
}

/*---------------------------------------------------------------------------*/
/* hifmc100_spi_lock */
/*---------------------------------------------------------------------------*/
void hifmc100_spi_lock(struct spinor_host *host, unsigned char level)
{
    unsigned char current_level;

    hifmc100_set_bp_level(host, level);

    /* check if we have set successfully or not */
    current_level = hifmc100_bp_to_level(host);
    if (current_level != level) {
        DBG_MSG("Error: Current lock level: %d, but set value: %d\n",
                current_level, level);
        return;
    }

    host->level = level;
    spi_lock_update_address(host);

    if (host->end_addr)
        printf("Spi is locked. lock address[0 => %#x]\n",
               host->end_addr);

    return;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_set_bp_level */
/*---------------------------------------------------------------------------*/
void hifmc100_set_bp_level(struct spinor_host *host, unsigned char level)
{
    unsigned char old_level;
    unsigned short val;
    unsigned int reg;
    struct spi *spi = host->spi;
    char mid = host->spinor->dev.id[0];

    MTD_PR(BP_DBG, "* Start BP bottom level %d\n", level);

    val = hifmc100_set_spi_lock_info(host);
    old_level = host->level;
    MTD_PR(BP_DBG, "  Read CR:SR[%#x]\n", val);

    if (old_level != level) {
        if (host->bp_num == BP_NUM_3) {
            val &= ~SPI_NOR_SR_BP_MASK_3;
        } else {
            val &= ~SPI_NOR_SR_BP_MASK_4;
        }
        val |= level << SPI_NOR_SR_BP0_SHIFT;
        MTD_PR(BP_DBG, "Set Status Register[%#x]\n", val);
    } else {
        MTD_PR(BP_DBG, "NOTES: old_level[%#x] = level[%#x]\n",
               old_level, level);
        return;
    }

    if (((mid == MID_ESMT) || ((mid == MID_MXIC)
                               && (spi->chipsize < _16M))) && (level == 0)) {
        val &= SPI_BP_BOTTOM_RDSR_SET_0(host->bp_num);
        MTD_PR(BP_DBG, "set level = 0[PB3 = 0]:[%#x]\n", val);
    }

    spi->driver->write_enable(spi);
    reg = reg_read(host, FMC_GLOBAL_CFG);
    if (reg & FMC_GLOBAL_CFG_WP_ENABLE) {
        MTD_PR(BP_DBG, " Hardware protected enable!, reg[%#x]\n", reg);
        reg &= ~FMC_GLOBAL_CFG_WP_ENABLE;
        reg_write(host, reg, FMC_GLOBAL_CFG);
        val &= ~SPI_NOR_SR_SRWD_MASK;
        MTD_PR(BP_DBG, "Disable SR[%d]:SRWD and WP#\n",
               SPI_NOR_SR_SRWD_SHIFT);
    }

    if (host->bt_loc == BT_LOC_RDSR) {
        writeb(val, host->membase);
        MTD_PR(BP_DBG, "Write IO[%p]%#x\n", host->membase,
               *(unsigned char *)host->membase);
    } else {
        writew(val, host->membase);
        MTD_PR(BP_DBG, "Write IO[%p]%#x\n", host->membase,
               *(unsigned short *)host->membase);
    }

    reg = FMC_CMD_CMD1(SPI_CMD_WRSR);
    reg_write(host, reg, FMC_CMD);
    MTD_PR(BP_DBG, " Set CMD[%#x]%#x\n", FMC_CMD, reg);

    reg = OP_CFG_FM_CS(spi->cs) | OP_CFG_OEN_EN;
    reg_write(host, reg, FMC_OP_CFG);
    MTD_PR(BP_DBG, " Set OP_CFG[%#x]%#x\n", FMC_OP_CFG, reg);

    if (host->bt_loc == BT_LOC_RDSR) {
        reg = FMC_DATA_NUM_CNT(SPI_NOR_SR_LEN);
    } else {
        reg = FMC_DATA_NUM_CNT(SPI_NOR_SR_LEN + SPI_NOR_CR_LEN);
    }

    reg_write(host, reg, FMC_DATA_NUM);
    MTD_PR(BP_DBG, " Set DATA_NUM[%#x]%#x\n", FMC_DATA_NUM, reg);

    reg = FMC_OP_CMD1_EN(ENABLE)
          | FMC_OP_WRITE_DATA_EN(ENABLE)
          | FMC_OP_REG_OP_START;
    reg_write(host, reg, FMC_OP);
    MTD_PR(BP_DBG, " Set OP[%#x]%#x\n", FMC_OP, reg);

    FMC_CMD_WAIT_CPU_FINISH(host);
    MTD_PR(BP_DBG, "* Set BP level end.\n");
}

/*---------------------------------------------------------------------------*/
/* hifmc100_bp_to_level */
/*---------------------------------------------------------------------------*/
unsigned char hifmc100_bp_to_level(struct spinor_host *host)
{
    unsigned char val;
    unsigned char level;
    struct spi *spi = host->spi;

    spi->driver->wait_ready(spi);
    val = hifmc100_read_reg(spi, SPI_CMD_RDSR);
    MTD_PR(BP_DBG, "Get Status Register[%#x]\n", val);

    MTD_PR(BP_DBG, "the bp_num[%d]\n", host->bp_num);

    if (host->bp_num == BP_NUM_3) {
        level = (val & SPI_NOR_SR_BP_MASK_3) >> SPI_NOR_SR_BP0_SHIFT;
    } else {
        level = (val & SPI_NOR_SR_BP_MASK_4) >> SPI_NOR_SR_BP0_SHIFT;
    }

    MTD_PR(BP_DBG, "the current level[%d]\n", level);

    return level;
}

/*---------------------------------------------------------------------------*/
/* spi_lock_update_address */
/*---------------------------------------------------------------------------*/
void spi_lock_update_address(struct spinor_host *host)
{
    unsigned int lock_level_max, erasesize, chipsize;
    char mid = host->spinor->dev.id[0];
    struct spi *spi = host->spi;

    if (!host->level) {
        host->end_addr = 0;
        MTD_PR(BP_DBG, "all blocks is unlocked.\n");
        return;
    }

    chipsize = spi->chipsize;
    erasesize = spi->erasesize;
    lock_level_max = host->spinor->bp_level_max;

    switch (mid) {
        case MID_MXIC:
            if (spi->chipsize == _2M) {
                if ((host->level != lock_level_max)
                        && (host->level != 1))
                    host->end_addr = chipsize - (erasesize <<
                                                 (lock_level_max - host->level - 1));
                else {
                    host->end_addr = chipsize;
                }
                return;
            }

            if (spi->chipsize != _8M) {
                break;
            }
        case MID_ESMT:
            /* this case is for ESMT and MXIC 8M devices */
            if (host->level != lock_level_max)
                host->end_addr = chipsize - (erasesize
                                             << (lock_level_max - host->level));
            else {
                host->end_addr = chipsize;
            }
            return;

        default:
            break;
    }

    /* general case */
    host->end_addr = chipsize >> (lock_level_max - host->level);
}

/*---------------------------------------------------------------------------*/
/* hifmc100_get_bp_lock_level */
/*---------------------------------------------------------------------------*/
void hifmc100_get_bp_lock_level(struct spinor_host *host)
{
    struct spi *spi = host->spi;
    char mid = host->spinor->dev.id[0];
    unsigned int lock_level_max;

    MTD_PR(BP_DBG, "Get manufacturer ID: [%#x]\n", mid);

    /* match the manufacture ID to get the block protect info */
    switch (mid) {
        case MID_GD:
        case MID_ESMT:
        case MID_CFEON:
        case MID_SPANSION:
            host->bp_num = BP_NUM_3;
            host->level = hifmc100_bp_to_level(host);
            break;
        case MID_WINBOND:
            if (spi->chipsize <= _16M) {
                host->bp_num = BP_NUM_3;
                host->level = hifmc100_bp_to_level(host);
            } else {
                host->bp_num = BP_NUM_4;
                host->level = hifmc100_bp_to_level(host);
            }
            break;
        case MID_MXIC:
            if (spi->chipsize <= _8M) {
                host->bp_num = BP_NUM_3;
                host->level = hifmc100_bp_to_level(host);
            } else {
                host->bp_num = BP_NUM_4;
                host->level = hifmc100_bp_to_level(host);
            }
            break;
        case MID_MICRON:
        case MID_PARAGON:
            return;
        default:
            goto usage;
    }

    /* this branch only for initialization */
    if (host->cmp == BP_CMP_UPDATE_FLAG) {
        /* get the max block protect level of current manufacture ID */
        if (host->bp_num == BP_NUM_4) {
            lock_level_max = LOCK_LEVEL_MAX(host->bp_num) - 5;
            /* just for MXIC(16M), the max lock level is 9 */
            if ((mid == MID_MXIC) && (spi->chipsize == _16M)) {
                lock_level_max--;
            }
        } else {
            lock_level_max = LOCK_LEVEL_MAX(host->bp_num);
        }

        host->spinor->bp_level_max = lock_level_max;
        MTD_PR(BP_DBG, "Get the max bp level: [%d]\n", lock_level_max);

        spi_lock_update_address(host);
        if (host->end_addr)
            printf("Spi is locked. lock address[0 => %#x]\n",
                   host->end_addr);
    }
    return;
usage:
    DBG_MSG("Error:The ID: %#x isn't in the BP table,\n", mid);
    DBG_MSG("Current device can't not protect\n");
}

/*---------------------------------------------------------------------------*/
/* hifmc100_set_spi_lock_info */
/*---------------------------------------------------------------------------*/
unsigned short hifmc100_set_spi_lock_info(struct spinor_host *host)
{
    unsigned short val;
    char mid = host->spinor->dev.id[0];
    struct spi *spi = host->spi;

    MTD_PR(BP_DBG, "Get manufacturer ID: [%#x]\n", mid);

    /* match the manufacture ID to get the block protect set info */
    switch (mid) {
        case MID_SPANSION:
            val = hifmc100_handle_bp_rdcr_info(host, SPI_CMD_RDCR);
            break;
        case MID_MXIC:
            if (spi->chipsize < _16M) {
                val = hifmc100_handle_bp_rdsr_info(host, SPI_CMD_RDSR);
            } else
                val = hifmc100_handle_bp_rdcr_info(host,
                                                   SPI_CMD_RDCR_MX);
            break;
        case MID_GD:
        case MID_ESMT:
        case MID_CFEON:
        case MID_WINBOND:
            val = hifmc100_handle_bp_rdsr_info(host, SPI_CMD_RDSR);
            break;
        default:
            goto usage;
    }

    return val;
usage:
    DBG_MSG("Error: The manufacture ID is change,\n Pleaer check!!\n");
    DBG_MSG("Error: The ID: %#x isn't in the BP table,\n", mid);
    return 1;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_handle_bp_rdcr_info */
/*---------------------------------------------------------------------------*/
unsigned short hifmc100_handle_bp_rdcr_info(struct spinor_host *host, u_char cmd)
{
    unsigned char status, config;
    struct spi *spi = host->spi;

    char mid = host->spinor->dev.id[0];

    /* this macro definition is for determining the writing length */
    host->bt_loc = BT_LOC_RDCR;
    spi->driver->wait_ready(spi);

    /* get the block protect B/P info in config register */
    config = hifmc100_read_reg(spi, cmd);
    MTD_PR(BP_DBG, "Get Config Register[%#x]\n", config);

    /* the location of T/B bit in config register is different.
       SPANSION is 5th and the MXIC(16/32M) is 3th */
    if (mid == MID_SPANSION) {
        config = SPI_BP_BOTTOM_RDCR_SET_S(config);
        MTD_PR(BP_DBG, "Set Config Register[%#x]\n", config);
    } else {
        config = SPI_BP_BOTTOM_RDCR_SET(config);
        MTD_PR(BP_DBG, "Set Config Register[%#x]\n", config);
    }

    /* get the block protect level info in status register */
    status = hifmc100_read_reg(spi, SPI_CMD_RDSR);
    MTD_PR(BP_DBG, "Get Status Register[%#x]\n", status);

    return ((unsigned short)config << SPI_NOR_CR_SHIFT) | status;
}

/*---------------------------------------------------------------------------*/
/* hifmc100_handle_bp_rdsr_info */
/*---------------------------------------------------------------------------*/
unsigned short hifmc100_handle_bp_rdsr_info(struct spinor_host *host,
                                            u_char cmd)
{
    unsigned char val;
    struct spi *spi = host->spi;
    char mid = host->spinor->dev.id[0];

    /* this macro definition is for determining the writing length */
    host->bt_loc = BT_LOC_RDSR;
    spi->driver->wait_ready(spi);

    /* get the block protect level and B/T info in status register */
    val = hifmc100_read_reg(spi, cmd);
    MTD_PR(BP_DBG, "Get Status Register[%#x]\n", val);
    if (mid == MID_CFEON) {
        val &= SPI_BP_BOTTOM_RDSR_SET_0(host->bp_num);
    } else {
        val |= SPI_BP_BOTTOM_RDSR_SET_1(host->bp_num);
    }
    MTD_PR(BP_DBG, "Set Config Register[%#x]\n", val);

    return val;
}

#endif

